/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.huawei.spare.part.replace.views.prepare.updatedb;

import com.google.common.base.Strings;
import com.huawei.spare.part.replace.Constants;
import com.huawei.spare.part.replace.OperationException;
import com.huawei.spare.part.replace.framework.SmartContentPresenter;
import com.huawei.spare.part.replace.framework.SmartDataBus;
import com.huawei.spare.part.replace.framework.SmartDatabase;
import com.huawei.spare.part.replace.framework.SmartRouter;
import com.huawei.spare.part.replace.models.DbMeta;
import com.huawei.spare.part.replace.rx.RxOkHttp;
import com.huawei.spare.part.replace.rx.RxUtils;
import com.huawei.spare.part.replace.utils.Application;
import com.huawei.spare.part.replace.utils.JsonMapper;
import com.huawei.spare.part.replace.utils.ZipUtils;
import com.huawei.spare.part.replace.views.widgets.Alerts;
import io.reactivex.*;
import io.reactivex.disposables.Disposable;
import io.reactivex.rxjavafx.observables.JavaFxObservable;
import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.stage.FileChooser;
import net.lingala.zip4j.exception.ZipException;
import okhttp3.Response;
import okio.BufferedSink;
import okio.Okio;
import org.controlsfx.glyphfont.FontAwesome;
import org.controlsfx.glyphfont.Glyph;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.filechooser.FileSystemView;
import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.List;

/**
 * @author Woo
 */
public class UpdateDbPresenter extends SmartContentPresenter {

    private static final Logger log = LoggerFactory.getLogger(UpdateDbPresenter.class);


    @FXML Glyph btnImportDbIcon;
    @FXML Button btnImportDb;

    @FXML Label dbVersion;
    @FXML Label dbUpdateOn;
    @FXML Label latestDbVersion;
    @FXML Label latestDbUpdateOn;
    @FXML Label latest;

    @FXML Label message;

    private SimpleObjectProperty<DbMeta> localDbMeta = new SimpleObjectProperty<DbMeta>();
    private SimpleObjectProperty<DbMeta> latestDbMeta = new SimpleObjectProperty<DbMeta>();

    @Override
    public void initialize() {
        // update local db meta
        JavaFxObservable.changesOf(localDbMeta).observeOn(JavaFxScheduler.platform())
                .subscribe((changed) -> {
                    final DbMeta meta = changed.getNewVal();
                    if (meta != null && !Strings.isNullOrEmpty(meta.getVersion())) {
                        dbVersion.setText(meta.getVersion());
                    } else {
                        dbVersion.setText(getString("common.message.not.installed"));
                    }

                    if (meta != null && !Strings.isNullOrEmpty(meta.getUpdatedOn())) {
                        dbUpdateOn.setText(meta.getUpdatedOn());
                    } else {
                        dbUpdateOn.setText(getString("common.message.not.installed"));
                    }

                    if (meta != null) {
                        compareDbVersion();
                    }

                    SmartRouter.get().nextStepButton().setDisable(localDbMeta.get().getVersion() == null);
                });

        // update latest db meta
        JavaFxObservable.changesOf(latestDbMeta).observeOn(JavaFxScheduler.platform())
                .subscribe((changed) -> {
                    final DbMeta latestMeta = changed.getNewVal();
                    if (latestMeta != null && !Strings.isNullOrEmpty(latestMeta.getVersion())) {
                        latestDbVersion.setText(latestMeta.getVersion());
                    } else {
                        latestDbVersion.setText(getString("common.message.load.failed"));
                    }
                    if (latestMeta != null && !Strings.isNullOrEmpty(latestMeta.getUpdatedOn())) {
                        latestDbUpdateOn.setText(latestMeta.getUpdatedOn());
                    } else {
                        latestDbUpdateOn.setText(getString("common.message.load.failed"));
                    }
                    compareDbVersion();
                });


        SmartRouter.get().nextStepButton().setDisable(true);
        Platform.runLater(() -> {
            try {
                SmartDatabase.get().reload(); // load local db
            } catch (IOException e) {
                log.info("Load db failed, reason: {}", e.getLocalizedMessage());
            }
            localDbMeta.set(SmartDatabase.get().meta(false));
        });
    }

    public void compareDbVersion() {
        final DbMeta latestDbMeta = this.latestDbMeta.get();
        boolean equal = false;
        if (latestDbMeta != null && !Strings.isNullOrEmpty(latestDbMeta.getVersion())) {
            if (latestDbMeta.getVersion().equals(localDbMeta.get().getVersion())) {
                equal = true;
            }
        }
        this.latest.setVisible(equal);
    }

    /**
     * update from remote db
     *
     * @param event
     */
    @FXML
    private void updatedb(ActionEvent event) {
        loading("updatedb.db.get.remote.meta", false);
        Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) throws Exception {
                try {
                    // query latest db meta
                    final String queryDbUpdateUrl = SmartDataBus.get().getConfigs().getQueryDbUpdateUrl();
                    log.info("Query DB update API url: {}", queryDbUpdateUrl);
                    final Response response = RxOkHttp.get().syncGet(queryDbUpdateUrl);
                    if (response.isSuccessful()) {
                        latestDbMeta.set(JsonMapper.nonDefaultMapper().getBean(response.body().byteStream(), DbMeta.class));
                        log.info("Remote db meta is: {}", latestDbMeta.get());
                    } else {
                        throw new OperationException(getString("common.title.api.exception"),
                                getString("updatedb.db.get.remote.meta.failed"));
                    }
                } catch (Exception e) {
                    latestDbMeta.set(new DbMeta());
                    throw new OperationException(e,
                            getString("common.title.request.failed"),
                            getString("updatedb.db.get.remote.meta.failed")
                    );
                }

                // download latest db if necessary
                final DbMeta local = localDbMeta.get();
                final DbMeta latest = latestDbMeta.get();
                if (local == null || Strings.isNullOrEmpty(local.getVersion())
                        || latest.getVersion().compareTo(local.getVersion()) > 0) {
                    emitter.onNext("updatedb.db.downloading");
                    final File destFile = Application.getTempFile(new Date().getTime() + ".zip");
                    try {
                        final String downloadUrl = latest.getDownload();
                        log.info("Start download latest db zip from: {}", downloadUrl);
                        final Response downloadResponse = RxOkHttp.get().syncGet(downloadUrl);
                        BufferedSink sink = Okio.buffer(Okio.sink(destFile));
                        sink.writeAll(downloadResponse.body().source());
                        sink.close();
                    } catch (Exception e) {
                        throw new OperationException(e,
                                getString("updatedb.db.download.failed"),
                                getString("common.title.request.failed")
                        );
                    }

                    importDbFile(destFile); // import db file
                    SmartDatabase.get().reload();
                    localDbMeta.set(SmartDatabase.get().meta(false)); // reload local db meta
                    destFile.delete();      // delete temp zip file
                } else {
                    SmartDatabase.get().reload();
                    emitter.onNext("updatedb.db.is.latest");
                }
                emitter.onComplete();
            }
        }).compose(RxUtils.<String>applySchedulers())
                  .subscribe(new Observer<String>() {
                      @Override
                      public void onSubscribe(Disposable d) {
                      }

                      @Override
                      public void onNext(String s) {
                          loading(s, false);
                      }

                      @Override
                      public void onError(Throwable e) {
                          dismissLoading();
                          Alerts.error(e);
                      }

                      @Override
                      public void onComplete() {
                          dismissLoading();
                      }
                  });
    }


    /**
     * select import db file button handler
     *
     * @param event
     */
    @FXML
    private void selectImportDbFile(ActionEvent event) {
        FileChooser chooser = new FileChooser();
        chooser.setTitle(getString("updatedb.db.file.chooser.title"));
        chooser.setInitialDirectory(FileSystemView.getFileSystemView().getHomeDirectory());
        chooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("database zip(*.zip)", "*.zip"));
        File file = chooser.showOpenDialog(SmartDataBus.get().getStage());
        if (file != null && importDbFile(file)) {
            loading("common.message.loading", false);
            Observable.defer(() -> {
                SmartDatabase.get().reload();
                return Observable.just("");
            }).compose(RxUtils.<String>applySchedulers()).subscribe((r) -> {
                btnImportDbIcon.setIcon(FontAwesome.Glyph.CHECK);
                localDbMeta.set(SmartDatabase.get().meta(false));
                dismissLoading();
            });
        }
    }

    /**
     * import db zip file to local db folder
     *
     * @param file
     * @return
     */
    private boolean importDbFile(File file) {
        try {
            final List<String> entryNames = ZipUtils.getDirectEntryNames(file);
            if (entryNames.containsAll(Constants.DbFiles)) {
                SmartDatabase.get().importDb(file);
                return true;
            }
            throw new OperationException(getString("updatedb.db.import.failed"),
                    getString("updatedb.db.zip.format.error"));
        } catch (IOException | ZipException e) {
            throw new OperationException(e, getString("updatedb.db.import.failed"),
                    getString("updatedb.db.zip.failed"));
        }
    }


    @Override
    public Observable<Boolean> onFinish() {
        final boolean localDbReady = localDbMeta.get() != null
                && !Strings.isNullOrEmpty(localDbMeta.get().getVersion());
        if (!localDbReady) {
            Alerts.error("common.title.can.not.continue", "updatedb.db.not.installed");
        }
        SmartDataBus.get().getProject().setDbMeta(localDbMeta.get());
        return Observable.just(localDbReady);
    }
}
